import java.io.File;
import java.io.FileNotFoundException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Scanner;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * vystupny formatovac, pouziva outputovy format, 
 * vola outputManager s kazdou referenciou ktoru vrati naformatovanu tato trieda
 * @author cerko
 *
 */
public class OutputFormatter {
	private File inputFormat;
	/**
	 * multi mapa dvojic typ referencie = zoznam moznych formatov na tento typ
	 */
	private HashMap<String, List<FormatList>> availableFormats;
	
	public OutputFormatter(File inputFile) throws FileNotFoundException{
		this.inputFormat = inputFile;
		availableFormats = new HashMap<String, List<FormatList>>();
		scanFormat();
	}
	
	/**
	 * naformatuje referenciu podla kluca a dat
	 * @param key - typ referencie eg. article, book etc.
	 * @param values - data referencie
	 * @return - naformatovana referencia alebo empty string ak nema definiciu 
	 * bud typu alebo referencie s danymi parametrami
	 */
	public String getformattedref(String key, Data values){
		List<FormatList> keysFormats = availableFormats.get(values.getType());
		if (keysFormats == null){
			//TODO do logu kto co kedy bla a return
			System.out.println("format for " + key + " undefined");
			return "";
		}
		
		String result = "";
		FormatList currentFormat = null;
		double currentSimilarity = 0;
		for (FormatList ll : keysFormats){
			if (ll.matchesTypes(values.getKeys())){
				currentFormat = ll;
				break;
			}
			else {
				/*
				if (ll.computeMatch(values) > currentSimilarity){
					currentSimilarity = ll.computeMatch(values);
					currentFormat = ll;
				}
				*/
			}
		}
		
		
		if (currentFormat != null){
			result += "\\bibitem{" + key + "}" + System.getProperty("line.separator");
			//System.out.println(evalList(values, currentFormat));
			result += evalList(values, currentFormat);
		}
		else {
			System.out.println("format undefined for : " + key);
			System.out.println("need definition for reference : " + values.getType());
			System.out.print("with keys : ");
			for (String debugKey : values.getKeys()){
				System.out.print(debugKey + ", ");
			}
			System.out.println(" ");
			return "";
		}
		
		
		return result;
	}
	
	/**
	 * vyhodnoti format list s datami s referencie
	 * @param data - data referencie
	 * @param format - formatlist referencie
	 * @return - vyhodnoteny cely format
	 */
	public String evalList(Data data, FormatList format){
		String result = "";
		ArrayList<Node> evaluatedFormat = format.getFormat();
		for (Node node : evaluatedFormat){
			if (node.needsEval()){
				result += data.getValue(node.getInfo());
			}
			else {
				result += node.getInfo();
			}
		}
		result = result.replaceAll("<br />", System.getProperty("line.separator"));
		return result;
	}
	
	/**
	 * zacne scanovanie formatu rozdeli na bloky podla @@ a posiela dalej
	 * @throws FileNotFoundException
	 */
	public void scanFormat() throws FileNotFoundException{
		Scanner scanner = new Scanner(inputFormat);
		scanner.useDelimiter("@@");
		while(scanner.hasNext()){
			scanBlock(scanner.next());
		}
		scanner.close();
		//System.out.println(availableFormats.toString());
	}
	
	/**
	 * scanuje jednotlive bloky nacita typ a posiela dalej
	 * @param block - scanovany text
	 */
	public void scanBlock(String block){
		Scanner scanner = new Scanner(block);
		String lineBreak = System.getProperty("line.separator");
		scanner.useDelimiter(lineBreak);
		
		String firstLine = scanner.next();
		String type = "";
		if (firstLine.contains("{")){
			//urez nastavenia a nastav typ
			Scanner settingsScanner = new Scanner(firstLine);
			
			settingsScanner.useDelimiter("\\{");
			type = settingsScanner.next();
			//type = type.replaceAll("@*", "");
			settingsScanner.useDelimiter("\\}");
			String settings = settingsScanner.next();
			settings = settings.replaceAll("\\{", "");
			
			if (Frontend.DEBUG){
				System.out.println(type);
				System.out.println(settings);
			}
			
			settingsScanner.close();
			
		}
		else {
			type = firstLine;
			//type = type.replaceAll("@*", "");
			
			//System.out.println(type);
		}
		
		//scanner.useDelimiter("");
		
		String whatsLeft = "";
		while (scanner.hasNext()){
			whatsLeft += scanner.next();
		}
		scanner.close();
		scanFormatPattern(type, whatsLeft);
		//System.out.println(whatsLeft);
	}
	
	/**
	 * naskenuje jednotlive definicie formatu a prida do formatlistu
	 * @param type - typ formatu eg. book, article etc.
	 * @param formatPattern - skenovany text
	 */
	public void scanFormatPattern(String type, String formatPattern){

		//fungujuci regex bac
		//(@[a-z]*(?=[^a-z]))|([^@a-z][^@]*)
		Matcher matcher = Pattern.compile("(@[a-z]*(?=[^a-z]))|([^@a-z][^@]*)", Pattern.DOTALL).matcher(formatPattern);
		
		FormatList formatList = new FormatList();
		while(matcher.find()){
			//System.out.println(matcher.group());
			
			formatList.add(matcher.group());
			//System.out.println(matcher.group(1));
			//System.out.println(matcher.group(2));
		}
		
		if (formatList.getSize() > 0){
			addFormatList(type, formatList);
			//list.add(formatList);
			//availableFormats.put(type, list);
		}

		
	}
	
	/**
	 * skontroluje ci uz neexistuje referencia s danym formatom(rovnake vsetky typy) a prida
	 * @param type - typ referencie
	 * @param formatList - formatlist definujuci format pre tuto referenciu
	 */
	public void addFormatList(String type, FormatList formatList){
		List<FormatList> formats = availableFormats.get(type);
		if (formats != null){
			for (FormatList ll : formats){
				if (formatsEqual(ll, formatList)){
					//TODO zapis do logu kto co kedy a return
					return;
				}
			}
		}
		availableFormatsInsert(type, formatList);
	}
	
	/**
	 * prida format List do multi mapy typ = List<formatlist>()
	 * @param type - typ referencie
	 * @param formatList - formatList rferencie
	 */
	public void availableFormatsInsert(String type, FormatList formatList){
		List<FormatList> list = availableFormats.get(type);
		if (list == null){
			list = new LinkedList<FormatList>();
		}
		list.add(formatList);
		availableFormats.put(type, list);
	}
	
	/**
	 * skontroluje ci sa zhoduju formaty referencii typmi
	 * @param first
	 * @param second
	 * @return ak sa zhoduju true inak false
	 */
	public boolean formatsEqual(FormatList first, FormatList second){
		return (first.matchesTypes(second.getTypes()));
	}
	
}
